﻿

/**
 * 模拟连接TravelSky的Eterm的客户端
 * 
 * @author hech
 * 
 */
public class Eterm implements EtermClient {

	static final Logger logger = LoggerFactory.getLogger(P350Eterm.class.getName());

	/**
	 * socket
	 */
	private Socket socket = null;

	/**
	 * EtermConfig
	 */
	private SaEterm config = null;

	/**
	 * 输出流
	 */
	BufferedOutputStream out;

	/**
	 * 输入流
	 */
	BufferedInputStream in;

	/**
	 * 同步锁
	 */
	private ReentrantLock locker = new ReentrantLock();

	/**
	 * 锁条件变量
	 */
	private Condition lockCondition = locker.newCondition();

	/**
	 * 锁定标记
	 */
	boolean isLocked = false;

	/**
	 * 是否连接上
	 */
	private boolean isConnected;

	/**
	 * 实时信息对象
	 */
	private EtermRealtimeInfo realtimeInfo = null;

	/**
	 * 协议
	 */
	private P350EtermProtocol protocol = new P350EtermProtocol();
	
	/**
	 * ips列表
	 */
	private String[] ips = null;
	
	/**
	 * 随机种子
	 */
	private Random random = new Random();
	

	/**
	 * 默认构造器,传入EtermConfig
	 * 
	 * @param config
	 */
	public P350Eterm(SaEterm config,String[] ips) {
		this.config = config;
		this.ips = ips;
		this.realtimeInfo = new EtermRealtimeInfo();
	}



	/**
	 * 获得一个随机ip
	 */
	private String getRandomIp(){
		if (ips.length==0)
			return null;
		else
			return ips[random.nextInt(ips.length)]; 
	}

	/**
	 * 锁定整个实例的访问 在这里，其实并没有使用ReentrantLock做实例锁定，而是使用它做了lock方法本身的锁定
	 * 因为ReentrantLock是基于线程的，所以如果出现死锁状态，无法使用外线程方便的解锁
	 * 
	 * @throws InterruptedException
	 * 
	 */
	public void lock() {
		this.realtimeInfo.setLastLockedTime(System.currentTimeMillis());
		this.realtimeInfo.addOneQueueCount();
		this.locker.lock();
		try {
			while (isLocked)
				lockCondition.await();
			isLocked = true;
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			this.locker.unlock();
		}
	}

	/**
	 * 解锁对整个实例的访问,此方法必须在finally块中 在此处没有加入对解锁条件的支持,主要是考虑系统可以无条件解任何锁
	 */
	public void unlock() {
		this.realtimeInfo.setLastIdleTime(System.currentTimeMillis());
		this.realtimeInfo.removeOneQueueCount();
		new DelayedUnLocker().start();
	}

	/**
	 * 延时解除锁定
	 * 
	 * @author zhangjin
	 * 
	 */
	class DelayedUnLocker extends Thread {

		public void run() {
			try {
				Thread.sleep(config.getOperInterval());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			locker.lock();
			try {
				isLocked = false;
				lockCondition.signal();
			} finally {
				locker.unlock();
			}
		}
	}

	/**
	 * 获得当前配置信息字符串
	 * 
	 * @return
	 */
	public String getConfigInfo() {
		return this.config.getUsername() + "@" + this.config.getHost() + ":" + this.config.getPort() + "|" + this.config.getOfficeId() + "|" + this.config.getSiUsername();
	}

	/**
	 * 获得配置信息
	 */
	public SaEterm getConfig() {
		return config;
	}

	/**
	 * 获得实时信息
	 */
	public EtermRealtimeInfo getRealtimeInfo() {
		return this.realtimeInfo;
	}

	/**
	 * 是否能连接
	 * 
	 * @return
	 */
	public boolean isConnected() {
		return isConnected && socket != null && out != null & in != null;
	}

	/**
	 * 关闭连接
	 * 
	 */
	public void disConnect() {
		isConnected = false;
		try {
			if (out != null)
				out.close();
			out = null;
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			if (in != null)
				in.close();
			in = null;
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			if (socket != null)
				socket.close();
			socket = null;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 重新连接
	 * 
	 */
	public void reConnect() {
		disConnect();
		connect();
	}

	/**
	 * 连接服务器
	 * 
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	public String connect() {
		// 如果已经连接,则不用连接了
		if (isConnected())
			return "已连接";
		// System.out.println(_username+"/"+_password);
		String retdata = "连接失败,未知原因";
		try {
			socket = new Socket();
			socket.connect(new InetSocketAddress(InetAddress.getByName(config.getHost()), config.getPort()), 30 * 1000);
			// socket.setTcpNoDelay(true);
			socket.setSoTimeout(30 * 1000);
			out = new BufferedOutputStream(socket.getOutputStream());
			in = new BufferedInputStream(socket.getInputStream());
			
			byte[] data = protocol.getLoginData(config.getUsername(), config.getPassword());
			// System.out.println("正在连接:"+host+":"+port+",使用帐户"+username+"/"+password);
			out.write(data);
			out.flush();
			EtermData edata = protocol.parseEtermData(new EtermData(null), in);
			if (protocol.checkLogin(edata.getSource())) {
				// 说明登陆成功
				out.write(protocol.getLoginSend17());
				out.flush();
				// 返回132字节的握手数据
				protocol.parseEtermData(new EtermData(null), in);
				// 发送握手数据,34字节
				out.write(protocol.getLoginSend34());
				out.flush();
				
				protocol.parseEtermData(new EtermData(null), in);
				
				out.write(protocol.getLoginSend260());
				out.flush();
				
				//protocol.parseEtermData(new EtermData(null), in);
				//protocol.parseEtermData(new EtermData(null), in);
				// 发送握手数据,68字节
				out.write(protocol.getLoginSend68());
				out.flush();				
				// 返回通知数据,中航信通知数据
				edata = protocol.parseEtermData(new EtermData(null), in);
				// 发送si的数据,因为不能自动登录
				siLogin();
				isConnected = true;
			} else {
				// 其他失败
				retdata = "连接失败," + edata.getData();
				throw new Exception();
			}
		} catch (Exception e) {
			retdata = "连接失败," + e.toString();
			this.disConnect();
			e.printStackTrace();
		}
		boolean isSuccessed = isConnected();
		if (isSuccessed)
			retdata = "连接成功";
		// 累加connectCount计数
		this.realtimeInfo.addConnectCount(1);
		if (!isSuccessed)
			this.realtimeInfo.addConnectErrorCount(1);
		return retdata;
	}

	/**
	 * si登陆
	 * 
	 * @throws Exception
	 */
	public EtermData siLogin() throws Exception {
		String cmd = null;
		if (!config.getSiUsername().equals("") && !config.getSiPassword().equals("")) {
			cmd = "si:" + config.getSiUsername() + "/" + config.getSiPassword() + (config.getSiLevel().equals("") ? "" : ("/" + config.getSiLevel()));
		}
		if (cmd != null)
			return internalSendCMD(cmd, new EtermData(cmd), false, false);
		else
			return null;
	}

	/**
	 * 发送命令
	 * 
	 * @return
	 * @throws IOException
	 */
	private EtermData internalSendCMD(String cmd, EtermData edata, boolean getSource, boolean checkSequence) throws Exception {
		long start = System.currentTimeMillis();
		byte[] senddata = protocol.getSendData(cmd);
		out.write(senddata);
		out.flush();
		if (checkSequence)
			edata = protocol.checkedParseEtermData(edata, in);
		else
			edata = protocol.parseEtermData(edata, in);
		// 累加write/read Bytes。
		this.realtimeInfo.addWriteBytes(senddata.length);
		if (edata.source != null)
			this.realtimeInfo.addReadBytes(edata.getSource().length);
		// 设定serviceCount
		this.realtimeInfo.addServiceCount(1);
		// 设定活动时间
		this.realtimeInfo.addActiveTime(System.currentTimeMillis() - start);
		if (edata != null && !getSource)
			edata.source = null;
		return edata;
	}

	/**
	 * 发送数据
	 * 
	 * @param cmd
	 * @return
	 */
	public EtermData sendCMD(String cmd, boolean getSource) {
		return sendCMD(cmd, config.getMaxTryTimes(), getSource);
	}

	/**
	 * 发送数据
	 * 
	 * @param cmd
	 * @param retrySendDataLImit
	 * @return
	 */
	public EtermData sendCMD(String cmd, int retrytimes, boolean getSource) {
		EtermData edata = new EtermData(cmd);
		// 要重试多次,防止有问题
		for (int trytimes = 0; trytimes < retrytimes; trytimes++) {
			try {
				edata = internalSendCMD(cmd, edata, getSource, true);
			} catch (Exception e) {
				System.out.println(edata.data);
				e.printStackTrace();
				// 网络连接不上的问题，重新连接
				logger.info("pid(" + config.getId() + ")关联的终端信息:" + getConfigInfo() + "遇到异常"+e.toString()+",正在重新连接...");
				reConnect();
			}
			if (edata.data != null && edata.data.indexOf("PLEASE WAIT - TRANSACTION IN PROGRESS") == -1) {
				edata.success = true;
				break;
			}
			// 增加失败计数
			this.realtimeInfo.addServiceErrorCount(1);
			edata.retryTimes++;
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
			}
		}
		return edata;
	}
	
}
